package jp.reflexworks.gae.util;

import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Date;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServletRequest;

import jp.sourceforge.reflex.util.DateUtil;

import org.apache.commons.codec.binary.Base64;

public class WsseUtil {

	// リクエストヘッダの項目名
	public static final String WSSE = "X-WSSE";
	public static final String TOKEN = "UsernameToken";
	public static final String USER = "Username";
	public static final String PASSWORDDIGEST = "PasswordDigest";
	public static final String NONCE = "Nonce";
	public static final String CREATED = "Created";
	
	// URLパラメータの項目名
	public static final String PARAM_USER = "user";
	public static final String PARAM_PASSWORDDIGEST = "digest";
	public static final String PARAM_NONCE = "nonce";
	public static final String PARAM_CREATED = "created";

	private Logger logger = Logger.getLogger(this.getClass().getName());

	/**
	 * WSSE情報を作成する(RequestHeader用)
	 */
	public String getWsseHeaderValue(String username, String password) {
		return getWsseHeaderValue(getWsse(username, password));
	}

	/**
	 * WSSE情報を作成する(RequestHeader用)
	 */
	public String getWsseHeaderValue(WsseAuth auth) {
		if (auth == null) {
			return "";
		}
		
		StringBuffer buf = new StringBuffer();
		buf.append(TOKEN);
		buf.append(" ");
		buf.append(USER);
		buf.append("=\"");
		buf.append(auth.username);
		buf.append("\", ");
		buf.append(PASSWORDDIGEST);
		buf.append("=\"");
		buf.append(auth.passwordDigest);
		buf.append("\", ");
		buf.append(NONCE);
		buf.append("=\"");
		buf.append(auth.nonce);
		buf.append("\", ");
		buf.append(CREATED);
		buf.append("=\"");
		buf.append(auth.created);
		buf.append('"');
		
		return buf.toString();
	}

	/**
	 * WSSE情報を作成する(URLパラメータ用)
	 */
	public String getWsseUrlParam(String username, String password) {
		return getWsseUrlParam(getWsse(username, password));
	}

	/**
	 * WSSE情報を作成する(URLパラメータ用)
	 */
	public String getWsseUrlParam(WsseAuth auth) {
		if (auth == null) {
			return "";
		}
		
		StringBuffer buf = new StringBuffer();
		buf.append(PARAM_USER);
		buf.append("=");
		buf.append(urlEncode(auth.username));
		buf.append("&");
		buf.append(PARAM_PASSWORDDIGEST);
		buf.append("=");
		buf.append(urlEncode(auth.passwordDigest));
		buf.append("&");
		buf.append(PARAM_NONCE);
		buf.append("=");
		buf.append(urlEncode(auth.nonce));
		buf.append("&");
		buf.append(PARAM_CREATED);
		buf.append("=");
		buf.append(urlEncode(auth.created));
		
		return buf.toString();
	}
	
	private String urlEncode(String str) {
		try {
			return URLEncoder.encode(str, "UTF-8");
		} catch (UnsupportedEncodingException e) {}
		return str;
	}

	/**
	 * WSSE情報を作成する
	 */
	public WsseAuth getWsse(String username, String password) {
		WsseAuth auth = null;
		
		byte[] nonceB = new byte[8];
		try {

			SecureRandom.getInstance("SHA1PRNG").nextBytes(nonceB);

			Date now = new Date();
			String created = DateUtil.getDateTime(now);

			byte[] createdB = created.getBytes("UTF-8");
			byte[] passwordB = password.getBytes("UTF-8");

			byte[] v = new byte[nonceB.length + createdB.length + passwordB.length];
			System.arraycopy(nonceB, 0, v, 0, nonceB.length);
			System.arraycopy(createdB, 0, v, nonceB.length, createdB.length);
			System.arraycopy(passwordB, 0, v, nonceB.length + createdB.length, passwordB.length);

			MessageDigest md = MessageDigest.getInstance("SHA1");
			md.update(v);
			byte[] digest = md.digest();
			
			auth = new WsseAuth();
			auth.username = username;
			auth.passwordDigest = new String(Base64.encodeBase64(digest), "UTF-8");
			auth.nonce = new String(Base64.encodeBase64(nonceB), "UTF-8");
			auth.created = created;

		} catch (NoSuchAlgorithmException e) {
			logger.log(Level.WARNING, e.getMessage(), e);
		} catch (UnsupportedEncodingException e) {
			logger.log(Level.WARNING, e.getMessage(), e);
		}
		return auth;
	}

	/**
	 * リクエストから認証処理を行う
	 */
	public boolean wsseAuthentication(HttpServletRequest req, String password) {
		// 認証情報の取り出し
		WsseAuth auth = getWsseAuth(req);
		
		// 認証チェック
		return checkAuth(auth, password);
	}
	
	/**
	 * リクエストから認証情報を取り出す
	 */
	public WsseAuth getWsseAuth(HttpServletRequest req) {
		WsseAuth auth = null;
		String wsse = req.getHeader(WsseUtil.WSSE);
		if (wsse != null) {
			// 認証情報はヘッダに指定
			auth = parseWSSEheader(wsse);
		} else {
			// 認証情報はURLパラメータに指定
			auth = parseWSSEparam(req);
		}
		
		return auth;
	}

	/**
	 * リクエストヘッダから認証情報の取り出し    
	 */
	public WsseAuth parseWSSEheader(String header) {
		String authUsername = null;
		String authPassworddigest = null;
		String authNonce = null;
		String authCreated = null;

		String[] words = header.split(",");
		int idx, i;

		for (i = 0; i < words.length; i++) {
			String rec = words[i];
			int len = rec.length();

			if (((idx = rec.indexOf('=')) > 0) && (idx < (len-1)))  {

				//String key = rec.substring(0, idx).toLowerCase().trim();
				String key = rec.substring(0, idx).trim();
				String val = rec.substring(idx+1, len).trim();
				int	stx = 0;
				int edx = val.length() - 1;
				char	c;
				if (((c = val.charAt(stx)) == '"') || (c == '\''))	stx++;
				if (((c = val.charAt(edx)) == '"') || (c == '\''))	edx--;
				val = val.substring(stx, edx+1);

				//System.out.println("KEY="+key+" / VAL="+val);

				if (key.indexOf(USER) >= 0) {
					authUsername = val;
				}
				else if (key.equals(PASSWORDDIGEST)) {
					authPassworddigest = val;
				}
				else if (key.equals(NONCE)) {
					authNonce = val;
				}
				else if (key.equals(CREATED)) {
					authCreated = val;
				}
			}
		}

		//認証パラメータの取り出せない場合は終了
		if ((authUsername != null) &&
				(authPassworddigest != null) &&
				(authNonce != null) &&
				(authCreated != null)) {
			WsseAuth auth = new WsseAuth();
			auth.username = authUsername;
			auth.passwordDigest = authPassworddigest;
			auth.nonce = authNonce;
			auth.created = authCreated;
			return auth;
		}
		
		return null;
	}

	/**
	 * URLパラメータから認証情報の取り出し    
	 */
	public WsseAuth parseWSSEparam(HttpServletRequest req) {
		String authUsername = req.getParameter(PARAM_USER);
		String authPassworddigest = req.getParameter(PARAM_PASSWORDDIGEST);
		String authNonce = req.getParameter(PARAM_NONCE);
		String authCreated = req.getParameter(PARAM_CREATED);

		//認証パラメータの取り出せない場合は終了
		if ((authUsername != null) &&
				(authPassworddigest != null) &&
				(authNonce != null) &&
				(authCreated != null)) {
			WsseAuth auth = new WsseAuth();
			auth.username = authUsername;
			auth.passwordDigest = authPassworddigest;
			auth.nonce = authNonce;
			auth.created = authCreated;
			return auth;
		}
		
		return null;
	}

	/**
	 * 認証チェック
	 */
	public boolean checkAuth(WsseAuth auth, String password) {
		if (auth == null) {
			return false;
		}

		try {
			//入力されたパラメータを取得
			byte[] digestB = Base64.decodeBase64(auth.passwordDigest.getBytes());
			byte[] nonceB = Base64.decodeBase64(auth.nonce.getBytes());
			byte[] createdB = auth.created.getBytes("utf-8");

			//指定パスワードからdigestを生成        	
			byte[] passwordB = password.getBytes("utf-8");
			byte[] v = new byte[nonceB.length + createdB.length + passwordB.length];
			System.arraycopy(nonceB, 0, v, 0, nonceB.length);
			System.arraycopy(createdB, 0, v, nonceB.length, createdB.length);
			System.arraycopy(passwordB, 0, v, nonceB.length + createdB.length, passwordB.length);

			MessageDigest md = MessageDigest.getInstance("SHA1");
			md.update(v);

			//digestを比較
			byte[] mdDigestB = md.digest();
			return MessageDigest.isEqual(mdDigestB, digestB);

		} catch (NoSuchAlgorithmException e) {
			logger.log(Level.WARNING, e.getMessage(), e);
		} catch (UnsupportedEncodingException e) {
			logger.log(Level.WARNING, e.getMessage(), e);
		}

		return false;
	}

	/**
	 * WSSEの項目
	 */
	public class WsseAuth {
		public String username;
		public String passwordDigest;
		public String nonce;
		public String created;
	}

}
